#include "demo.h"



int epd_server_start(epd_server_t* const server, int port)
{
	if(EPD_UNLIKELY(server == NULL) || EPD_UNLIKELY(port <= 0))
	{
		EPD_ERR("invalid parametesr, server=%p, port=%d.\n", server, port);
		return -EPD_INVALID_PARAM;
	}
	
	server->listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(server->listen_fd < 0)
	{
		EPD_ERR("create socket failed, %d.\n", errno);
		return EPD_FAILED;
	}
	
	struct sockaddr_in	server_addr;
	bzero(&server_addr, sizeof(server_addr));
	server_addr.sin_family		= AF_INET;
	server_addr.sin_addr.s_addr	= htons(INADDR_ANY);
	server_addr.sin_port		= htons(port);
	if(bind(server->listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) > 0)
	{
		EPD_ERR("bind failed, %d.\n", errno);
		close(server->listen_fd);
		return EPD_FAILED;
	}
//	EPD_INF("server bind port %d success.\n", port);
	
	if(listen(server->listen_fd, EPD_SERVER_LISTEN_QUEUE))
	{
		EPD_ERR("server listen failed, %d.\n", errno);
		close(server->listen_fd);
		return EPD_FAILED;
	}
	
	server->epfd = epoll_create(EPD_SERVER_EPOLL_FD_MAX);
	if(server->epfd < 0)
	{
		EPD_ERR("create epoll failed, %d.\n", errno);
		close(server->listen_fd);
		return EPD_FAILED;
	}
	
	if(epd_epoll_fd_add(server->epfd, server->listen_fd, EPD_FALSE) != EPD_OK)
	{
		EPD_ERR("add sock epoll failed, %d.\n", errno);
		close(server->epfd);
		close(server->listen_fd);
		return EPD_FAILED;
	}
	
//	EPD_INF("server listen success.\n");
	return EPD_OK;
}


int epd_server_stop(epd_server_t* const server)
{
	if(EPD_UNLIKELY(server == NULL))
	{
		EPD_ERR("invalid parametesr, server=%p.\n", server);
		return -EPD_INVALID_PARAM;
	}
	
	close(server->epfd);
	close(server->listen_fd);
	return EPD_OK;
}


EPD_LOCAL uint64_t epd_conn_num = 0;

int epd_server_loop(epd_server_t* const server, int (*proc)(int sockfd, int epollfd, void* data), 
		void* data, int* is_break)
{
	struct epoll_event* events = NULL;
	
	if(EPD_UNLIKELY(server == NULL) || EPD_UNLIKELY(proc == NULL))
	{
		EPD_ERR("invalid parameters, sserver=%p, proc=%p.\n", server, proc);
		return -EPD_INVALID_PARAM;
	}
	
	events = malloc(sizeof(*events) * EPD_SERVER_EPOLL_EVT_MAX);
	if(events == NULL)
	{
		EPD_ERR("no enough memory.\n");
		return -EPD_NO_MEMORY;
	}
	memset(events, 0, sizeof(*events) * EPD_SERVER_EPOLL_EVT_MAX);
	
	while(*is_break != EPD_TRUE)
	{
		int ret = epoll_wait(server->epfd, events, EPD_SERVER_EPOLL_EVT_MAX, 500);
		
		if(ret == 0)
		{
			continue;
		}
		
		if(ret < 0)
		{
			if(errno == EINTR)
			{
				continue;
			}
			
			EPD_ERR("epoll failed, ret=%d, errno=%d.\n", ret, errno);
			break;
		}
		
		int i = 0;
		for(i=0; i<ret; i++)
		{
			const int sockfd = events[i].data.fd;
			if(sockfd == server->listen_fd)
			{
				struct sockaddr_in client_addr;
				socklen_t len = sizeof(client_addr);
				int conn_fd = accept(server->listen_fd, (struct sockaddr*)&client_addr, &len);
				
				if(conn_fd < 0)
				{
					EPD_ERR("accept error, %d.\n", errno);
					break;
				}
//				EPD_INF("accept connect from client %s\n", inet_ntoa(client_addr.sin_addr));
				
				epd_epoll_fd_add(server->epfd, conn_fd, EPD_TRUE);
				epd_conn_num ++;
			}
			else if(events[i].events & EPOLLIN)
			{
				if(proc(sockfd, server->epfd, data) != EPD_OK)
				{
//					EPD_OUT("Client Disconnected, Remove it.\n");
					if(epoll_ctl(server->epfd, EPOLL_CTL_DEL, sockfd, &events[i]) != 0)
					{
						EPD_ERR("Client Remove Failed, errno %d.\n", errno);
					}
					else
					{
//						EPD_OUT("Client Remove OK.\n");
						epd_conn_num --;
					}
				}
				else
				{
					epd_epoll_oneshot_reset(server->epfd, sockfd);
				}
			}
			else if(events[i].events & EPOLLHUP)
			{
				EPD_INF("connect reflushed.\n");
			}
			else
			{
				EPD_INF("something else happened.\n");
			}
		}
	}
	
	free(events);
	return EPD_OK;
}


EPD_LOCAL uint64_t epd_run_time = 0;
EPD_LOCAL struct timeval epd_last_tm;

EPD_LOCAL void epd_timer_proc(int signum)
{
	EPD_LOCAL last_conn_num = 0;
	struct timeval tm;
	int interval = 0;
	
	epd_run_time ++;
	gettimeofday(&tm, NULL);
	
	EPD_OUT("Current Run Time : %05lu, ", epd_run_time);
	EPD_OUT("Connect Number   : %08lu, ", epd_conn_num);
	
	interval = tm.tv_sec * 1000000 + tm.tv_usec - epd_last_tm.tv_sec * 1000000 - epd_last_tm.tv_usec;
	if(epd_conn_num >= last_conn_num)
	{
		EPD_OUT("Connect Speed    : %05ld.\n", (epd_conn_num - last_conn_num) * 1000000 / interval);
	}
	else
	{
		EPD_OUT("Connect Speed    : -%05ld.\n", (last_conn_num - epd_conn_num) * 1000000 / interval);
	}
	
	epd_last_tm.tv_sec 	= tm.tv_sec;
	epd_last_tm.tv_usec	= tm.tv_usec;
	last_conn_num		= epd_conn_num;
}

EPD_LOCAL void epd_timer_init(void)
{
	struct itimerval value, ovalue;
	
	signal(SIGALRM, epd_timer_proc);
	
	value.it_value.tv_sec = 1;
	value.it_value.tv_usec = 0;
	value.it_interval.tv_sec = 1;
	value.it_interval.tv_usec = 0;
	setitimer(ITIMER_REAL, &value, &ovalue);
	
	gettimeofday(&epd_last_tm, NULL);
	EPD_OUT("Start - PID %d, [%ld.%09ld] %s", getpid(), epd_last_tm.tv_sec, epd_last_tm.tv_usec, 
			asctime(localtime(&epd_last_tm.tv_sec)));
}


int epd_server_proc(int sockfd, int epollfd, void* data)
{
	epd_req_t req;
	
	int ret = recv(sockfd, &req, sizeof(req), MSG_DONTWAIT | MSG_WAITALL);
	if(EPD_UNLIKELY(ret != sizeof(req)))
	{
		if((ret < 0) && ((errno == EAGAIN) || (errno == EINTR) || (errno == EWOULDBLOCK)))
		{
			return EPD_OK;
		}
		
		if(ret != 0)
		{
			EPD_ERR("recv failed, ret=%d(%ld), errno=%d.\n", ret, sizeof(req), errno);
		}
		return EPD_FAILED;
	}
	req.val ++;
	
//	EPD_DBG("[%lu] receive %s - %s", req.idx, req.name, req.info);
	
	if(send(sockfd, &req, sizeof(req), MSG_WAITALL) != sizeof(req))
	{
		EPD_ERR("[%lu] response failed, %s - %s", req.idx, req.name, req.info);
		return EPD_FAILED;
	}
	
	return EPD_OK;
}


EPD_LOCAL int is_break = EPD_FALSE;

EPD_LOCAL void epd_server_intc(int signum)
{
	is_break = EPD_TRUE;
}


int main(int argc, const char* argv[])
{
	int server_port	= atoi("9999");
	epd_server_t server;
	
	if(argc < 2)
	{
		EPD_OUT("server <listen port>.\n");
		return -EPD_INVALID_PARAM;
	}
	server_port	= atoi(argv[1]);
	
	if(server_port <= 1000)
	{
		EPD_ERR("invalid parameters, server_port %d,.\n", server_port);
		return -EPD_INVALID_PARAM;
	}
	
	if(signal(SIGINT, epd_server_intc) == SIG_ERR)
	{
		EPD_ERR("install ctrl+c signal failed.\n");
		return EPD_FAILED;
	}
	
	epd_timer_init();
	
	if(epd_server_start(&server, server_port) != EPD_OK)
	{
		EPD_ERR("start server failed.\n");
	}
	EPD_OUT("Start Server At Port %d OK.\n", server_port);
	
	epd_server_loop(&server, epd_server_proc, NULL, &is_break);
	
	epd_server_stop(&server);
	return 0;
}


